package com.ccp.dev.workflow.model;

import com.ccp.dev.core.util.AppUtil;
import com.ccp.dev.core.util.BeanUtils;
import com.ccp.dev.core.util.Dom4jUtil;
import com.ccp.dev.workflow.dao.ActDefModelDao;
import com.ccp.dev.workflow.service.ActDefModelService;
import com.ccp.dev.workflow.service.BpmnXmlService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.Element;
import org.flowable.bpmn.model.*;
import org.flowable.bpmn.model.Process;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.repository.Model;

import java.util.*;

/**
 * ClassDescribe:
 * 流程定义节点Cache,系统维护两份Cache, 一份是flowable本身维护的Cache，主要用于流程跳转，由flowable本身引擎使用，
 * 而另一套流程定义的Cache则由本系统维护，用于取得每个节点的前后链接及父子关系等。
 * @author :wangcheng
 * Date: 2019-06-18
 */
public class NodeCache {
    private static final Log logger = LogFactory.getLog(NodeCache.class);

    /**
     * 键为流程定义id，值为流程节点Map对象，paraTypeMap的键为节点id，值为节点对象。
     */
    private static Map<String, Map<String, FlowNodeInfo>> actNodesMap = new HashMap<>();

    /**
     * 根据流程定义id获取流程的节点
     *  1.首先去缓存中查找如果找到直接返回该流程的节点集合。
     *  2.不存在直接在流程中查询xml并解析成节点结合，并放到缓存中进行返回。
     * @param procDefId 流程定义id
     * @return 流程节点信息
     */
    public static synchronized Map<String, FlowNodeInfo> getByProcDefId(String procDefId) {
        if (actNodesMap.containsKey(procDefId)) {
            return actNodesMap.get(procDefId);
        } else {
            Map<String, FlowNodeInfo> map = readFromBpmnModel(procDefId, null);

            actNodesMap.put(procDefId, map);
            return map;
        }
    }

    /**
     * 通过BPMNModel构造节点信息
     * @param procDefId 流程定义id
     * @return 流程节点信息
     */
    public  static Map<String, FlowNodeInfo> readFromBpmnModel(String procDefId, FlowNodeInfo parentNode) {
        RepositoryService repositoryService = AppUtil.getBean(RepositoryService.class);
        ActDefModelService actDefModelService = AppUtil.getBean(ActDefModelService.class);
        Map<String, FlowNodeInfo> map = new HashMap<>();
        BpmnModel bpmnModel = repositoryService.getBpmnModel(procDefId);
        Process mainProcess = bpmnModel.getMainProcess();

        for (FlowElement element : mainProcess.getFlowElements()) {

            String nodeType = element.getClass().getSimpleName();
            String nodeId = element.getId();
            String nodeName = element.getName();
            boolean isMultiInstance = false;
            if (element instanceof Activity) {
                isMultiInstance = ((Activity) element).getLoopCharacteristics() != null;
            }

            // 节点类型
            // 开始节点，用户任务，并行网关，条件网关，分支网关，结束节点，自动任务节点。
            if (FlowNodeInfo.TYPE_START_EVENT.equalsIgnoreCase(nodeType)
                    || FlowNodeInfo.TYPE_USERTASK .equalsIgnoreCase(nodeType)
                    || FlowNodeInfo.TYPE_PARALLELGATEWAY.equalsIgnoreCase(nodeType)
                    || FlowNodeInfo.TYPE_INCLUSIVEGATEWAY.equalsIgnoreCase(nodeType)
                    || FlowNodeInfo.TYPE_EXCLUSIVEGATEWAY.equalsIgnoreCase(nodeType)
                    || FlowNodeInfo.TYPE_END_EVENT.equalsIgnoreCase(nodeType)
                    || FlowNodeInfo.TYPE_SERVICETASK.equalsIgnoreCase(nodeType)
                    || FlowNodeInfo.TYPE_SENDTASK.equalsIgnoreCase(nodeType)
                    || FlowNodeInfo.TYPE_SCRIPTTASK.equalsIgnoreCase(nodeType)){
                FlowNodeInfo nodeInfo = new FlowNodeInfo(nodeId, nodeName, nodeType, isMultiInstance);
                if (FlowNodeInfo.TYPE_START_EVENT.equalsIgnoreCase(nodeType)) {
                    nodeInfo.setParentNode(parentNode);

                }
                map.put(nodeId, nodeInfo);
            }
            // 子流程
            else if (FlowNodeInfo.TYPE_SUBPROCESS.equalsIgnoreCase(nodeType)) {
                map = getSubInnerProFlowNode(map, nodeId, nodeName, nodeType, isMultiInstance, element);
            }

            // 外部子流程
            else if (FlowNodeInfo.TYPE_CALLACTIVITY.equalsIgnoreCase(nodeType)) {
                String flowKey = ((CallActivity) element).getCalledElement();
                ActDefModel actDefModel = actDefModelService.getMainByDefKey(flowKey);
                FlowNodeInfo flowNodeInfo = new FlowNodeInfo(nodeId, nodeName, nodeType, isMultiInstance);
                Map<String, FlowNodeInfo> subChildNodes = readFromBpmnModel(actDefModel.getProcDefId(), flowNodeInfo);
                flowNodeInfo.setAttribute("subFlowKey", flowKey);
                map.put(nodeId, flowNodeInfo);
                // 设置子流程的接单
                flowNodeInfo.setSubProcessNodes(subChildNodes);
            }
        }

        for (FlowElement element : mainProcess.getFlowElements()) {
            fillNodeInfo(map, element);
        }
        return map;
    }

    private static void fillNodeInfo(Map<String, FlowNodeInfo> map, FlowElement element) {
        if (element instanceof SequenceFlow) {
            SequenceFlow flow = (SequenceFlow) element;
            String sourceRef = flow.getSourceRef();
            String targetRef = flow.getTargetRef();

            FlowNodeInfo sourceFlowNode = map.get(sourceRef);
            FlowNodeInfo targetFlowNode = map.get(targetRef);
            if (sourceFlowNode != null && targetFlowNode != null) {
                logger.debug("sourceRef:" + sourceFlowNode.getNodeName()
                        + " targetRef:" + targetFlowNode.getNodeName());
                if (targetFlowNode.getParentNode() != null) {
                    logger.debug("parentNode:" + targetFlowNode.getParentNode().getNodeName());
                }

                if (FlowNodeInfo.TYPE_START_EVENT.equalsIgnoreCase(sourceFlowNode.getNodeType())
                        && sourceFlowNode.getParentNode() == null) {
                    targetFlowNode.setIsFirstNode(true);
                }

                // 设置子流程中的第一个任务节点
                if (FlowNodeInfo.TYPE_START_EVENT.equalsIgnoreCase(sourceFlowNode.getNodeType())
                        && sourceFlowNode.getParentNode() != null) {
                    targetFlowNode.setIsFirstNode(true);
                    sourceFlowNode.getParentNode().setSubFirstNode(targetFlowNode);
                    // 当子流程为多实例时，也认为子流程内的其第一个任务节点也是多实例
                    if (targetFlowNode.getParentNode() != null && targetFlowNode.getParentNode().getIsMultiInstance()) {
                        targetFlowNode.setIsMultiInstance(true);
                    }
                }

                sourceFlowNode.getNextFlowNodesInfo().add(targetFlowNode);
                targetFlowNode.getPreFlowNodesInfo().add(sourceFlowNode);
            }
        } else if (element instanceof SubProcess) {
            Collection<FlowElement> subElements = ((SubProcess) element).getFlowElements();
            for (FlowElement subElement : subElements) {
                fillNodeInfo(map, subElement);
            }
        }
    }

    /**
     * 获取子流程的节点信息
     * @param map 节点信息集合
     * @param nodeId 节点id
     * @param nodeName 节点名称
     * @param nodeType 节点类型
     * @param isMultiInstance 是否是多实例
     * @param element 当前元素
     * @return 节点信息集合
     */
    private static Map<String, FlowNodeInfo> getSubInnerProFlowNode(Map<String, FlowNodeInfo> map, String nodeId, String nodeName, String nodeType, boolean isMultiInstance, FlowElement element) {
        // 产生子流程的映射
        FlowNodeInfo subProcessNode = new FlowNodeInfo(nodeId, nodeName, nodeType, isMultiInstance, new ArrayList<FlowNodeInfo>());
        // 将子流程节点放到map 对象中
        map.put(nodeId, subProcessNode);
        // 遍历子流程的节点
        SubProcess subProcess = (SubProcess) element;
        Collection<FlowElement> flowElements = subProcess.getFlowElements();
        for (FlowElement subElement : flowElements) {
            String subNodeType = subElement.getClass().getSimpleName();
            String subNodeId = subElement.getId();
            String subNodeName = subElement.getName();
            boolean subIsMultiInstance = false;
            if (subElement instanceof Activity) {
                subIsMultiInstance = ((Activity) subElement).getLoopCharacteristics() != null;
            }

            // 节点类型
            // 开始节点，用户任务，并行网关，条件网关，分支网关，结束节点，自动任务节点。
            if (FlowNodeInfo.TYPE_START_EVENT.equalsIgnoreCase(subNodeType)
                    || FlowNodeInfo.TYPE_USERTASK .equalsIgnoreCase(subNodeType)
                    || FlowNodeInfo.TYPE_PARALLELGATEWAY.equalsIgnoreCase(subNodeType)
                    || FlowNodeInfo.TYPE_INCLUSIVEGATEWAY.equalsIgnoreCase(subNodeType)
                    || FlowNodeInfo.TYPE_EXCLUSIVEGATEWAY.equalsIgnoreCase(subNodeType)
                    || FlowNodeInfo.TYPE_END_EVENT.equalsIgnoreCase(subNodeType)
                    || FlowNodeInfo.TYPE_SERVICETASK.equalsIgnoreCase(subNodeType)){
                FlowNodeInfo nodeInfo = new FlowNodeInfo(subNodeId, subNodeName, subNodeType, false, subProcessNode);
                subProcessNode.getSubFlowNodes().add(nodeInfo);
                map.put(subNodeId, nodeInfo);
            }
            // 子流程
            else if (FlowNodeInfo.TYPE_SUBPROCESS.equalsIgnoreCase(subNodeType)) {
                map = getSubInnerProFlowNode(map, subNodeId, subNodeName, subNodeType, subIsMultiInstance, subElement);
            }

            // 外部子流程
            else if (FlowNodeInfo.TYPE_CALLACTIVITY.equalsIgnoreCase(subNodeType)) {
                String flowKey = ((CallActivity) subElement).getCalledElement();
                ActDefModelService actDefModelService = AppUtil.getBean(ActDefModelService.class);
                ActDefModel actDefModel = actDefModelService.getMainByDefKey(flowKey);
                FlowNodeInfo flowNodeInfo = new FlowNodeInfo(subNodeId, subNodeName, subNodeType, subIsMultiInstance);
                subProcessNode.getSubFlowNodes().add(flowNodeInfo);
                // 设置子流程的节点
                Map<String, FlowNodeInfo> subChildNodes = readFromBpmnModel(actDefModel.getProcDefId(), flowNodeInfo);
                flowNodeInfo.setAttribute("subFlowKey", flowKey);
                map.put(subNodeId, flowNodeInfo);
                // 设置子流程的接单
                flowNodeInfo.setSubProcessNodes(subChildNodes);
            }
        }

        return map;
    }

    /**
     * 获取流程的起始节点
     * @param procDefId 流程定义id
     * @return 流程节点
     */
    public static FlowNodeInfo getStartNode(String procDefId) {
        // 确保流程取得流程定义节点
        getByProcDefId(procDefId);
        Map<String, FlowNodeInfo> nodeInfoMap = actNodesMap.get(procDefId);
        return getStartNode(nodeInfoMap);
    }

    /**
     * 获取任务节点的起始节点
     * 只获取主流程和外部子流程的开始节点
     * @param nodeInfoMap 节点信息集合
     * @return 开始节点
     */
    public static FlowNodeInfo getStartNode(Map<String, FlowNodeInfo> nodeInfoMap) {
        for (FlowNodeInfo nodeInfo : nodeInfoMap.values()) {
            if ("startEvent".equalsIgnoreCase(nodeInfo.getNodeType())) {
                FlowNodeInfo parentNode = nodeInfo.getParentNode();
                if (parentNode == null || "callActivity".equalsIgnoreCase(parentNode.getNodeType())) {
                    return nodeInfo;
                }
            }
        }

        return null;
    }

    /**
     * 获取第一个节点列表
     * @param procDefId 流程定义id
     * @return
     */
    public static List<FlowNodeInfo> getFirstNodeList(String procDefId) {
        FlowNodeInfo startNode = getStartNode(procDefId);
        if (startNode == null) {
            return new ArrayList<>();
        }
        return startNode.getNextFlowNodesInfo();
    }

    /**
     * 获取第一个节点
     * @param procDefId 流程定义id
     * @return 第一个节点信息
     * @throws Exception 异常处理
     */
    public static FlowNodeInfo getFirstNodeInfo(String procDefId) throws Exception {
        FlowNodeInfo startNode = getStartNode(procDefId);
        if (startNode == null) {
            return null;
        }
        List<FlowNodeInfo> nextFlowNodesInfo = startNode.getNextFlowNodesInfo();
        if (nextFlowNodesInfo.size() > 1) {
            return null;
        }
        if (nextFlowNodesInfo.size() == 0) {
            throw new Exception("流程定义：" + procDefId + ",起始节点没有后续节点");
        }

        return nextFlowNodesInfo.get(0);
    }

    /**
     * 判断起始节点后是否有多个实例节点
     * @param procDefId 流程定义id
     * @return true or false
     * @throws Exception
     */
    public static boolean isMultipleFirstNode(String procDefId) throws Exception {
        FlowNodeInfo startNode = getStartNode(procDefId);
        if (startNode == null) {
            return false;
        }
        List<FlowNodeInfo> nextFlowNodesInfo = startNode.getNextFlowNodesInfo();
        if (nextFlowNodesInfo.size() == 0) {
            throw new Exception("流程定义：" + procDefId + ",起始节点没有后续节点");
        }

        return nextFlowNodesInfo.size() > 1;

    }

    /**
     * 判断是否是第一个节点
     * @param procDefId 流程定义id
     * @param nodeId 节点id
     * @return true or false
     */
    public static boolean isFirstNode(String procDefId,String nodeId){
        List<FlowNodeInfo>  list = getFirstNodeList(procDefId);
        for (FlowNodeInfo flowNode : list) {
            if (nodeId.equals(flowNode.getNodeId())) {
                return true;
            }
        }
        return false;
    }

    /**
     * 判断节点是否是会签节点。
     * @param procDefId 流程定义id
     * @param nodeId 节点id
     * @return true or false
     */
    public static boolean isSignTaskNode(String procDefId,String nodeId){
        getByProcDefId(procDefId);
        Map<String, FlowNodeInfo> nodeMap = actNodesMap.get(procDefId);
        FlowNodeInfo flowNode=nodeMap.get(nodeId);
        if(flowNode.getIsMultiInstance() && FlowNodeInfo.TYPE_USERTASK.equalsIgnoreCase(flowNode.getNodeType())){
            return true;
        }
        return false;

    }

    /**
     * 获取流程的结束节点。
     *
     * @param procDefId
     *            流程定义ID。
     * @return 返回流程的节点。
     */
    public static List<FlowNodeInfo> getEndNode(String procDefId) {
        // 确保流程取得流程定义节点。
        getByProcDefId(procDefId);
        Map<String, FlowNodeInfo> nodeMap = actNodesMap.get(procDefId);
        return getEndNode(nodeMap);
    }

    /**
     * 获取流程的结束节点
     *
     * @param nodeMap 流程节点信息
     * @return 节点信息
     */
    private static List<FlowNodeInfo> getEndNode(Map<String, FlowNodeInfo> nodeMap) {
        List<FlowNodeInfo> flowNodeList = new ArrayList<FlowNodeInfo>();
        for (FlowNodeInfo flowNode : nodeMap.values()) {
            if ("endEvent".equalsIgnoreCase(flowNode.getNodeType())
                    && BeanUtils.isEmpty(flowNode.getNextFlowNodesInfo())) {
                flowNodeList.add(flowNode);
            }
        }
        return flowNodeList;
    }

    /**
     * 根据流程定义id获取流程是否有外部子流程。
     *
     * @param procDefId
     *            流程定义ID。
     * @return true or false
     */
    public static boolean hasExternalSubprocess(String procDefId) {
        getByProcDefId(procDefId);
        Map<String, FlowNodeInfo> nodeMap = actNodesMap.get(procDefId);
        for (FlowNodeInfo flowNode : nodeMap.values()) {
            if ("callActivity".equals(flowNode.getNodeType())) {
                return true;
            }
        }
        return false;
    }

    /**
     * 根据流程定义id删除缓存数据。
     *
     * @param procDefId 流程定义ID。
     */
    public static void clear(String procDefId) {
        actNodesMap.remove(procDefId);
    }

    /**
     * 根据流程定义ID和节点ID获取
     * @param procDefId
     * @param nodeId
     * @return
     */
    public static FlowNodeInfo getNodeByActNodeId(String procDefId, String nodeId) {
        // 确保流程取得流程定义节点
        getByProcDefId(procDefId);
        Map<String, FlowNodeInfo> nodeInfoMap = actNodesMap.get(procDefId);
        return  nodeInfoMap.get(nodeId);
    }


    public static FlowNodeInfo getFirstNodeId(String procDefId) throws Exception {
        FlowNodeInfo startNode = getStartNode(procDefId);
        if (startNode == null) {
            return null;
        } else {
            List<FlowNodeInfo> list = startNode.getNextFlowNodesInfo();
            if (list.size() > 1) {
                return null;
            } else if (list.size() == 0) {
                throw new Exception("流程定义:" + procDefId + ",起始节点没有后续节点，请检查流程图设置!");
            } else {
                return list.get(0);
            }
        }
    }

    public static Set<String> getSubKeysByXml(String xml) {
        Set<String> keySet = new HashSet();
        getSubKeysByXml(xml, (Set)keySet);
        return keySet;
    }
    private static void getSubKeysByXml(String xml, Set<String> keySet) {
        xml = xml.replace("xmlns=\"http://www.omg.org/spec/BPMN/20100524/MODEL\"", "");
        Document document = Dom4jUtil.loadXml(xml);
        Element el = document.getRootElement();
        Element processEl = (Element)el.selectSingleNode("./process");
        if (!BeanUtils.isEmpty(processEl)) {
            Iterator its = processEl.elementIterator();

            while(its.hasNext()) {
                Element nodeEl = (Element)its.next();
                String nodeType = nodeEl.getName();
                if ("CallActivity".equalsIgnoreCase(nodeType)) {
                    String flowKey = nodeEl.attributeValue("calledElement");
                    keySet.add(flowKey);
                    String subProcessXml = b(flowKey);
                    getSubKeysByXml(subProcessXml, keySet);
                }
            }

        }
    }
    private static String b(String procDefKey) {
        ActDefModelDao actDefModelDao = AppUtil.getBean(ActDefModelDao.class);
        ActDefModel actDefModel = actDefModelDao.getXmlByProcDefKey(procDefKey);
        BpmnXmlService bpmnXmlService = AppUtil.getBean(BpmnXmlService.class);
        String xml = bpmnXmlService.generateBpmn20Xml(actDefModel);
        return xml;
    }
}
